""" 
打开文件
对文件对象进行读/写操作
关闭文件
"""

""" 
读/写文件的能力是由操作系统提供的，操作系统不允许普通程序直接操作磁盘，
所以读/写文件时需要请求操作系统打开一个对象，这就是我们在程序中要操作的文件对象。

读写文件¶
open() 返回一个 file object ，最常使用的是两个位置参数和一个关键字参数：open(filename, mode, encoding=None)
f = open('workfile', 'w', encoding="utf-8")

第一个实参是文件名字符串。
第二个实参是包含描述文件使用方式字符的字符串。
  mode 的值包括 
    'r' ，表示文件只能读取；会从文件开头读取文件，如果文件不存在会报错。
    'w' 表示只能写入（现有同名文件会被覆盖）；会从文件开头写文件，如果文件不存在会创建新文件。
    'a' 表示打开文件并追加内容，任何写入的数据会自动添加到文件末尾。如果文件不存在会创建新文件。
    'r+' 表示打开文件进行读写功能。
    'w+' 表示打开文件进行读写功能。
    'a+' 表示打开文件进行读写功能。
    'b' 表示以二进制模式打开文件。默认是't'，文本形式打开。
        二进制模式的数据是以 bytes 对象的形式读写的。
        在二进制模式下打开文件时，你不能指定 encoding 。

  mode 实参是可选的，省略时的默认值为 'r'。
  以上几种可以搭配使用：例如ab、wb、ab、ab+

  默认模式为 'r' （打开文件用于读取文本，与 'rt' 同义）。
  'w+' 和 'w+b' 模式将打开文件并清空内容。
  而 'r+' 和 'r+b' 模式将打开文件但不清空内容。

通常情况下，文件是以 text mode 打开的，
也就是说，你从文件中读写字符串，这些字符串是以特定的 encoding 编码的。
如果没有指定 encoding ，默认的是与平台有关的（见 open() ）。
因为 UTF-8 是现代事实上的标准，除非你知道你需要使用一个不同的编码，否则建议使用 encoding="utf-8" 。

在文本模式下读取文件时，默认把平台特定的行结束符（Unix 上为 \n, Windows 上为 \r\n）转换为 \n。
在文本模式下写入数据时，默认把 \n 转换回平台特定结束符。
这种操作方式在后台修改文件数据对文本文件来说没有问题，但会破坏 JPEG 或 EXE 等二进制文件中的数据。
注意，在读写此类文件时，一定要使用二进制模式。

os.close(fd)
  关闭文件描述符 fd。
  注解 该功能适用于低级 I/O 操作，必须用于 os.open() 或 pipe() 返回的文件描述符。
  若要关闭由内建函数 open()、popen() 或 fdopen() 返回的 "文件对象"，则应使用其相应的 close() 方法。

f = open('file', 'r')
try:
  content = f.read()
except:
  pass
finally:
  f.close()

如果没有使用 with 关键字，则应调用 f.close() 关闭文件，即可释放文件占用的系统资源。
警告 调用 f.write() 时，未使用 with 关键字，或未调用 f.close()，即使程序正常退出，也**可能** 导致 f.write() 的参数没有完全写入磁盘。

在处理文件对象时，最好使用 with 关键字。
优点是，子句体结束后，文件会正确关闭，即便触发异常也可以。
而且，使用 with 相比等效的 try-finally 代码块要简短得多：

with open('file', 'r', encoding="utf-8") as f:
...  read_data = f.read()

>>> # We can check that the file has been automatically closed.
>>> f.closed

通过 with 语句，或调用 f.close() 关闭文件对象后，再次使用该文件对象将会失败。

>>> f.close()
>>> f.read()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: I/O operation on closed file.

"""

""" 
with open("secret.txt", 'r+') as file:    # 空文件会报错
with open("secret.txt", 'w+') as file:    # 读写模式打开文件  并清空文件内容

with open('secret.txt', 'r') as file:     
  # 报错  UnicodeDecodeError: 'gbk' codec can't decode byte 0x80 in position 8: illegal multibyte sequence
  # 编码错误
  print(file.read())
"""

with open(r"secret.txt", 'r', encoding="utf-8") as file:    # r"xxxx.txt" r表示路径的斜杠不转义
  print(file.read())

""" 
文件对象的方法
  本节下文中的例子假定已创建 f 文件对象。

  f.read(size) 可用于读取文件内容，它会读取一些数据，并返回字符串（文本模式），或字节串对象（在二进制模式下）。 
  size 是可选的数值参数。
  省略 size 或 size 为负数时，读取并返回整个文件的内容；文件大小是内存的两倍时，会出现问题。
  size 取其他值时，读取并返回最多 size 个字符（文本模式）或 size 个字节（二进制模式）。
  如已到达文件末尾，f.read() 返回空字符串（''）。
"""

""" 
  f.readline() 从文件中读取单行数据；字符串末尾保留换行符（\n），只有在文件不以换行符结尾时，文件的最后一行才会省略换行符。
  这种方式让返回值清晰明确；只要 f.readline() 返回空字符串，就表示已经到达了文件末尾，空行使用 '\n' 表示，该字符串只包含一个换行符。
  从文件中读取多行时，可以用循环遍历整个文件对象。这种操作能高效利用内存，快速，且代码简单：
    >>> for line in f:
    ...     print(line, end='')

  如需以列表形式读取文件中的所有行，可以用 list(f) 或 f.readlines()。

  f.write(string) 把 string 的内容写入文件，并返回写入的字符数。
  >>> f.write('This is a test\n')

  写入其他类型的对象前，要先把它们转化为字符串（文本模式）或字节对象（二进制模式）：
  >>> value = ('the answer', 42)
  >>> s = str(value)  # convert the tuple to string
  >>> f.write(s)
"""

with open(r'secret.txt', 'r', encoding="utf-8") as file:
  print(file.readline())      # 默认是数组中的第一项

with open(r'secret.txt', 'r', encoding="utf-8") as file:
  content = file.readlines()  # 是整个数组
  print(content)

with open(r'secret.txt', 'r', encoding='utf-8') as file:
  content = file.readlines()
  for item in content:
    print(item.rstrip('\n'))      # rstrip()  去除空格


""" 
  f.tell() 返回整数，给出文件对象在文件中的当前位置，表示为二进制模式下时从文件开始的字节数，以及文本模式下的意义不明的数字。

  f.seek(offset, whence) 可以改变文件对象的位置。
  通过向参考点添加 offset 计算位置；参考点由 whence 参数指定。 
  whence 值为 0 时，表示从文件开头计算，1 表示使用当前文件位置，2 表示使用文件末尾作为参考点。
  省略 whence 时，其默认值为 0，即使用文件开头作为参考点。
  >>> f = open('workfile', 'rb+')
  >>> f.write(b'0123456789abcdef')
  16
  >>> f.seek(5)      # Go to the 6th byte in the file
  5
  >>> f.read(1)
  b'5'
  >>> f.seek(-3, 2)  # Go to the 3rd byte before the end
  13
  >>> f.read(1)

  在文本文件（模式字符串未使用 b 时打开的文件）中，只允许相对于文件开头搜索（使用 seek(0, 2) 搜索到文件末尾是个例外），唯一有效的 offset 值是能从 f.tell() 中返回的，或 0。
  其他 offset 值都会产生未定义的行为。
  文件对象还支持 isatty() 和 truncate() 等方法，但不常用；文件对象的完整指南详见库参考。
"""

# with open(r'secret.txt', 'r', encoding="utf-8") as file:    # 会报错，只读模式，不支持写入
with open(r'secret.txt', 'a', encoding="utf-8") as file:
  file.write("\n译文\n茫茫的海上升起一轮明月，你我相隔天涯却共赏月亮。\n多情的人都怨恨月夜漫长，整夜里不眠而把亲人怀想。\n熄灭蜡烛怜爱这满屋月光，我披衣徘徊深感夜露寒凉。\n不能把美好的月色捧给你，只望能够与你相见在梦乡。")
  
with open(r"secret.txt", 'r', encoding="utf-8") as file:
  print(file.read())